package cn.zhoujing.security.demo.filter;

import cn.zhoujing.security.demo.common.consts.RedisConst;
import cn.zhoujing.security.demo.common.properties.JwtProperties;
import cn.zhoujing.security.demo.common.utils.JwtProvider;
import cn.zhoujing.security.demo.common.utils.RedisUtil;
import cn.zhoujing.security.demo.entity.UserDetail;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.TypeReference;
import jakarta.annotation.Resource;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author zhoujing
 * @createTime 2024/2/8 - 20:06
 */
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Resource
    private JwtProvider jwtProvider;

    @Resource
    private JwtProperties jwtProperties;

    @Resource
    private RedisUtil redisUtil;

    final static Type type = new TypeReference<UserDetail>() {
    }.getType();

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        log.info("JWT过滤器通过T请求头部Token自动登录……");

        // 获取请求头部中的Authorization
        String token = request.getHeader("Authorizations");
        // token是否为空，并且是否是指定前缀
        if (StringUtils.hasText(token) && token.startsWith(jwtProperties.getTokenPrefix())) {
            // 去除前缀，拿到真是token
            token = token.substring(jwtProperties.getTokenPrefix().length());
            // 拿到用户id
            String userId = jwtProvider.getSubjectFromToken(token);

            if (StringUtils.hasText(userId)) {
                // 从缓存中获取用户信息
                String userDetailStr = (String) redisUtil.get(RedisConst.userDetailInfoKeyBuild(userId));
                UserDetail userDetail = JSON.parseObject(userDetailStr, type);
                // 角色codes额外手动拷贝
                JSONObject jsonObject = JSON.parseObject(userDetailStr);
                JSONArray authorities = jsonObject.getJSONArray("authorities");
                if (!CollectionUtils.isEmpty(authorities)) {
                    Set<String> roleCodes = authorities.stream().map(author -> JSON.parseObject(author.toString()).getString("authority")).collect(Collectors.toSet());
                    Set<SimpleGrantedAuthority> authoritySet = roleCodes.stream().map(role -> new SimpleGrantedAuthority(role.toString())).collect(Collectors.toSet());
                    userDetail.setAuthorities(authoritySet);
                }

                // 反向验证token
                if (Objects.nonNull(userDetail) && jwtProvider.validateToken(token, userDetail)) {
                    // 构建 authentication 对象
                    UsernamePasswordAuthenticationToken authenticationToken =
                            new UsernamePasswordAuthenticationToken(userDetail.getUsername(), userDetail.getPassword(), userDetail.getAuthorities());
                    // 将认证信息存储至ThreadLocal
                    SecurityContext context = SecurityContextHolder.createEmptyContext();
                    context.setAuthentication(authenticationToken);
                    SecurityContextHolder.setContext(context);
                    log.info("JWT过滤器通过校验请求头token自动登录成功, user : {}", userDetail.getUsername());
                }
            }
        }
        filterChain.doFilter(request, response);
    }
}
